/**
 * main viewer
 * 主頁推薦(預設)
 * {
 *   type: 'main-viewer',
 *   mediaType: MEDIA_TYPE,
 *   index: { foryou: 0 },
 *   fetchers: {
 *     foryou: useInfiniteForyou(),
 *   },
 * }
 *
 * or
 *
 * 其他位置
 * 透過 setFromMainViewer 指定 fetcher
 * 可以選擇 video 和 photo 只給其中之一，這樣播放器上方會將 tab 隱藏
 * {
 *   type: 'main-viewer',
 *   mediaType: MEDIA_TYPE,
 *   index: { video: 0, photo: 0 },
 *   fetchers: {
 *     video: 由外部自行決定傳給 setFromMainViewer,
 *     photo: 由外部自行決定傳給 setFromMainViewer,
 *   },
 * }
 *
 * 行為:
 *  1. 上下滑動切換展示項目，滑到倒數第2個觸發繼續更新
 *  2. 點擊大頭貼或往左滑動可以導至 creator(預先載入此頁)
 *  3. 點擊 #tag 可以導至 tag-list
 *
 *
 * tag list
 * {
 *   type: 'tag-list,
 *   tag: `${tag}`,
 *   mediaType: MEDIA_TYPE,
 *   index: { video: 0, photo: 0 },
 *   fetchers: {
 *     video: useInfinite({ request: API.Search.tagDetail, params: { type: MEDIA_TYPE.VIDEO, keyword: `${tag}` } }),
 *     photo: useInfinite({ request: API.Search.tagDetail, params: { type: MEDIA_TYPE.PHOTO, keyword: `${tag}` } }),
 *   },
 * }
 * 行為:
 *  1. 往下滑觸發繼續更新目前的 media tab 清單
 *  2. 點擊任何 media 或往左滑動可以導至 tag-viewer
 *
 *
 * creator
 * {
 *   type: 'creator',
 *   userId: `${userId}`,
 *   userName: `${userName}`, // 不一定會有，setFromCreator 有給的時候才有
 *   mediaType: MEDIA_TYPE,
 *   index: { free: 0, subs: 0, shop: 0 },
 *   fetchers: {
 *     free: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 1 } }),
 *     subs: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 5 } }),
 *     shop: useInfinite({ request: API.Media.getPostList, params: { aff: id, visible: 10 } }),
 *   },
 * }
 * 行為:
 *  1. 往下滑觸發繼續更新目前的 media tab 清單
 *  2. 點擊任何 media 或往左滑動可以導至 creator-viewer
 *
 *
 * tag viewer
 * {
 *   type: 'tag-viewer',
 * }
 * 行為:
 *  1. 上下滑動切換展示項目，滑到倒數第2個觸發前頁(tag-list)繼續更新
 *  2. 往左滑或點擊大頭貼可導至 creator(預先載入此頁)
 *  3. 點擊 #tag 可以導至 tag-list
 *
 *
 * creator viewer
 * {
 *   type: 'creator-viewer',
 * }
 * 行為:
 *  1. 上下滑動切換展示項目，滑到倒數第2個通知前頁(creator)觸發繼續更新
 *  2. 點擊大頭貼可以導回上一頁 creator
 *  3. 點擊 #tag 可以導至 tag-list
 *
 *
 * 流程圖如下，後續可無限生長
 *
 *            /--> creator --> creator viewer --> tag-list
 * main viewer
 *            \                            /--> creator --> creator viewer
 *             \--> tag list --> tag viewer
 *                                         \--> tag-list -- tag viewer
 */
import { computed, onActivated, onDeactivated, readonly, ref, toRef, watch } from 'vue'
import { useRafFn, whenever } from '@vueuse/core'
import { useCreatorStore } from '@/store/creator'
import { contains as containsLocale, locale } from '@/i18n'
import { useInfinite } from '@/compositions/request/infinite'
import { MEDIA_TYPE } from '@/constant'
import {
  CREATOR,
  CREATOR_VIEWER,
  MAIN_VIEWER,
  MAIN_VIEWER_TYPE,
  MEDIA_TYPE_LABELS,
  TAG_LIST,
  TAG_VIEWER,
} from '@/constant/viewer-router'
import { trackEvent } from '@/gtm'
import API from '@/http'

export function useMobileViewerRouter(viewerName) {
  const DEFAULT_REVISE_STACK_MILLISECOND = 400
  const STACK_REVISING_ACTION_QUEUE = []
  let ORIGIN_PATH = window.location.pathname
  let reviseStackTimeoutId = null
  let reviseStackFn = null

  // page id
  const idMaker = (function* () {
    let id = 0
    while (true) {
      yield id++
    }
  })()

  const { getMedia } = useCreatorStore()

  const stack = ref([])
  const currIndex = ref(0)

  // 語言切換時要同步路徑
  watch(locale, (lang) => {
    const pathArr = ORIGIN_PATH.split('/')
    if (containsLocale(pathArr[1])) {
      pathArr[1] = lang
      ORIGIN_PATH = pathArr.join('/')
    }
  })

  // 某些場景會是整個播放器都在畫面之外的，必須把此改為 false 讓某些行為可以判別停止，例如視頻暫停播放
  const active = ref(true)
  let activeTemp
  onActivated(() => {
    if (typeof activeTemp === 'boolean') {
      active.value = activeTemp
    } else {
      active.value = true
    }
  })
  onDeactivated(() => {
    activeTemp = active.value
    active.value = false
  })

  // 正在從目前頁面返回第一頁
  const isBackingToFirst = ref(false)

  // 供外部用來關閉播放器 transition 效果
  // 例如正在從目前貼文前往指定索引貼文時，可能橫跨太多則，此時跑動畫會有點怪
  const disableTransition = ref(false)

  // MAIN_VIEWER 目前有兩種
  // 1. 主頁推薦
  // 2. 其他頁影片/照片
  const mainViewerType = ref(MAIN_VIEWER_TYPE.FORYOU)

  // first page active rate
  // 介於 0~1 之間的數值，和 css 的 opacity 一樣，可以說就是要給他用的
  const fpar = ref(1)
  function setFPAR(v) {
    if (currIndex.value > 1) {
      return
    }
    if (v <= 0) {
      fpar.value = 0
    } else if (v >= 1) {
      fpar.value = 1
    } else {
      fpar.value = Number(v.toFixed(2))
    }
  }
  const TRANSITION_DURATION = 250
  let prevTs
  const { resume: transitFPAR, pause } = useRafFn(
    ({ timestamp }) => {
      if (currIndex.value <= 1) {
        const delta = timestamp - (prevTs || timestamp)
        const deltaFpar = Number(parseFloat(delta / TRANSITION_DURATION).toFixed(2))
        fpar.value += currIndex.value === 0 ? deltaFpar : -deltaFpar
        prevTs = timestamp
        if (fpar.value > 1) {
          fpar.value = 1
          stopTransitFPAR()
        } else if (fpar.value < 0) {
          fpar.value = 0
          stopTransitFPAR()
        }
      } else {
        stopTransitFPAR()
      }
    },
    { immediate: false },
  )
  function stopTransitFPAR() {
    pause()
    prevTs = null
  }
  watch(currIndex, (v) => {
    if (v <= 1) {
      transitFPAR()
    }
  })

  const noData = computed(() => {
    try {
      const page = getPage(currIndex.value)
      let targetPage
      if (page.type === MAIN_VIEWER) {
        targetPage = page
      }
      if ([TAG_VIEWER, CREATOR_VIEWER].includes(page.type)) {
        targetPage = getPage(currIndex.value - 1)
      }
      if (targetPage) {
        return targetPage.fetchers[MEDIA_TYPE_LABELS[targetPage.mediaType]]?.dataList.length === 0
      }
    } catch (e) {
      return true
    }
    return false
  })

  // for debug
  // watch(stack, (v) => console.log(viewerName, v), { deep: true })

  let fromWhichPage

  function setFromMainViewer(fetchers) {
    reset(MAIN_VIEWER, {}, fetchers)
  }

  function setFromCreator(userId, userName) {
    reset(CREATOR, { userId, userName })
  }

  function setFromTagList(tag, { interest, catename } = {}) {
    reset(TAG_LIST, { tag, ...(interest && { interest }), ...(catename && { catename }) })
  }

  function reset(from, { userId, userName, tag, interest, catename }, mainViewerFetchers) {
    // reviseStack 尚未被執行，將此執行放進 action queue 當中
    if (reviseStackTimeoutId !== null) {
      STACK_REVISING_ACTION_QUEUE.push(() => reset(from, { userId, userName, tag }, mainViewerFetchers))
      return
    }

    fromWhichPage = from

    // 清空
    popPage(0)
    currIndex.value = 0

    switch (from) {
      case MAIN_VIEWER:
        pushPage(MAIN_VIEWER, {}, mainViewerFetchers)
        pushPage(CREATOR)
        break
      case CREATOR:
        pushPage(CREATOR, { userId, userName })
        pushPage(CREATOR_VIEWER)
        break
      case TAG_LIST:
        pushPage(TAG_LIST, { tag, ...(interest && { interest }), ...(catename && { catename }) })
        pushPage(TAG_VIEWER)
        break
      default:
        throw new Error('Something went wrong...')
    }

    changePath()
  }

  /**
   * route 往後移動一頁
   * 會伴隨著預判好的下一頁插入
   */
  function next() {
    if (reviseStackTimeoutId !== null) return

    if (currIndex.value >= stack.value.length - 1) return

    currIndex.value += 1

    const currPage = getPage(currIndex.value)

    reviseStack(() => {
      const prevPage = getPage(currIndex.value - 1)
      const feed = getTriggerRouteFeed(currIndex.value - 1)

      switch (currPage.type) {
        case CREATOR: {
          pushPage(CREATOR_VIEWER)

          // the 埋
          if (feed) {
            trackEvent({ key: 1, meta: { id: feed.id } })
          }

          break
        }
        case TAG_LIST:
          pushPage(TAG_VIEWER)

          // the 埋
          if (feed) {
            trackEvent({ key: 10, meta: { id: feed.id, tag: currPage.tag } })
          }

          break
        case TAG_VIEWER:
          pushPage(CREATOR)

          // the 埋
          if (feed) {
            if (prevPage.mediaType === MEDIA_TYPE.VIDEO) {
              trackEvent({ key: 16, meta: { id: feed.id, aff: feed.author.aff } })
            }
            if (prevPage.mediaType === MEDIA_TYPE.PHOTO) {
              trackEvent({ key: 17, meta: { id: feed.id, aff: feed.author.aff } })
            }
          }

          break
        case CREATOR_VIEWER:
          // the 埋
          if (feed) {
            if (prevPage.mediaType === MEDIA_TYPE.FREE) {
              trackEvent({ key: 70, meta: { id: feed.id, aff: feed.author.aff } })
            }
            if (prevPage.mediaType === MEDIA_TYPE.SUBS) {
              trackEvent({ key: 71, meta: { id: feed.id, aff: feed.author.aff } })
            }
            if (prevPage.mediaType === MEDIA_TYPE.SHOP) {
              trackEvent({ key: 72, meta: { id: feed.id, aff: feed.author.aff } })
            }
          }
          break
      }
    })
  }

  /**
   * route 往前移動一頁
   * 會伴隨著後面不需要的頁面被清除，並添加預判好的下一頁
   */
  function prev() {
    if (reviseStackTimeoutId !== null) return

    if (currIndex.value <= 0) return

    currIndex.value -= 1

    const currPage = getPage(currIndex.value)
    const nextPage = getPage(currIndex.value + 1)

    reviseStack(() => {
      if (CREATOR !== currPage.type) {
        popPage()
      }

      if ([MAIN_VIEWER, TAG_VIEWER].includes(currPage.type) && TAG_LIST === nextPage.type) {
        popPage()
        pushPage(CREATOR)
      }

      if (CREATOR_VIEWER === currPage.type) {
        popPage()
      }
    })
  }

  /**
   * 返回第一頁
   *
   * 這邊採取一次遞減一頁
   * 讓外部可以跑轉場效果
   * 會與 routeComplete 搭配
   * 持續將 currIndex 減至 0
   *
   * 當 currIndex 減至 0 的時候
   * 後續頁面都會被清掉只剩下第一頁和第二頁
   */
  function backToFirst() {
    if (reviseStackTimeoutId !== null) return

    if (currIndex.value === 0) return

    isBackingToFirst.value = true

    currIndex.value -= 1

    if (currIndex.value > 0) {
      reviseStack(() => {})
    } else {
      isBackingToFirst.value = false

      const firstPage = getPage(0)
      const secondPage = getPage(1)

      reviseStack(() => {
        if (fromWhichPage === MAIN_VIEWER) {
          if (MAIN_VIEWER === firstPage.type) {
            if (CREATOR === secondPage.type) {
              popPage(2)
            }
            if (TAG_LIST === secondPage.type) {
              popPage(1)
              pushPage(CREATOR)
            }
          }
        }

        if (fromWhichPage === CREATOR) {
          if (CREATOR === firstPage.type && CREATOR_VIEWER === secondPage.type) {
            popPage(2)
          }
        }
      })
    }
  }

  /**
   * 當外部曾經呼叫過 prev 或 next 等 route 函式
   * 可以等到轉場效果完成後(@transitionend)執行此函式
   * 通知內部可以進行 revise stack 了
   *
   * 若是在預設時間內沒有任何外部呼叫此函式
   * 那麼就會被直接執行防止 revise stack 沒有發生
   *
   * 最好是由外部轉場效果結束後馬上呼叫
   * 這麼做是最精確的
   */
  function routeComplete() {
    if (reviseStackTimeoutId === null) {
      return
    }

    clearTimeout(reviseStackTimeoutId)
    reviseStackTimeoutId = null

    if (reviseStackFn === null) {
      return
    }

    reviseStackFn()
    reviseStackFn = null

    // reviseStackFn 執行完成後將緩存中的事件執行完
    STACK_REVISING_ACTION_QUEUE.forEach((action) => action())
    STACK_REVISING_ACTION_QUEUE.length = 0

    if (isBackingToFirst.value) {
      backToFirst()
    }
  }

  /**
   * route 往後移動到下一頁 TAG_LIST 頁面
   * 必須是 MAIN_VIEWER, TAG_VIEWER, CREATOR_VIEWER 才能前往
   * 否則拋出錯誤
   *
   * @param {String} tag 下一頁 TAG_LIST 的 tag
   */
  function nextToTagList(tag) {
    if (reviseStackTimeoutId !== null) return

    const currPage = getPage(currIndex.value)

    if ([MAIN_VIEWER, TAG_VIEWER].includes(currPage.type)) {
      // Tag 播放器若點選的 tag 跟目前 tag 相同直接拉回前一頁
      if (TAG_VIEWER === currPage.type) {
        const prevPage = getPage(currIndex.value - 1)
        if (prevPage.tag === tag) {
          prev()
          return
        }
      }

      // 拿掉 CREATOR PAGE
      popPage()

      pushPage(TAG_LIST, { tag })
    } else if (CREATOR_VIEWER === currPage.type) {
      pushPage(TAG_LIST, { tag })
    } else {
      throw new Error(`${viewerName}: ${currPage.type} page cannot to TAG_LIST page`)
    }

    requestAnimationFrame(next)
  }

  /**
   * route 往後移動到下一頁指定索引的 TAG_VIEWER 或 CREATOR_VIEWER 頁面
   * 必須是 TAG_LIST, CREATOR 才能前往
   * 否則拋出錯誤
   *
   * @param {Number} index 下一頁 VIEWER 要定位的索引
   */
  function nextToIndexedViewer(index) {
    if (reviseStackTimeoutId !== null) return

    const currPage = getPage(currIndex.value)

    if ([TAG_LIST, CREATOR].includes(currPage.type)) {
      currPage.index[MEDIA_TYPE_LABELS[currPage.mediaType]] = index
    } else {
      throw new Error(`${viewerName}: ${currPage.type} page cannot to VIEWER page`)
    }

    disableTransition.value = true
    requestAnimationFrame(() => {
      next()
      disableTransition.value = false
    })
  }

  /**
   * 設定 MAIN_VIEWER 頁面到指定索引
   * 目前頁面必須為 MAIN_VIEWER
   * 否則拋出錯誤
   *
   * @param {Number} index 下一頁 VIEWER 要定位的索引
   */
  function toIndexedMainViewer(index) {
    if (reviseStackTimeoutId !== null) return

    const currPage = getPage(currIndex.value)

    if (MAIN_VIEWER === currPage.type) {
      currPage.index[MEDIA_TYPE_LABELS[currPage.mediaType]] = index
    } else {
      throw new Error(`${viewerName}: ${currPage.type} page is not MAIN_VIEWER, it cannot call toIndexedMainViewer()`)
    }

    disableTransition.value = true
    requestAnimationFrame(() => (disableTransition.value = false))
  }

  function getMediaType(index) {
    const page = getPage(index)

    if ([MAIN_VIEWER, TAG_LIST, CREATOR].includes(page.type)) {
      return makeMediaTypeRef(page)
    }

    if ([TAG_VIEWER, CREATOR_VIEWER].includes(page.type)) {
      const prevPage = getPage(index - 1)
      return makeMediaTypeRef(prevPage)
    }

    throw new Error(`${viewerName}: It's imposible to reach here...`)
  }

  function makeMediaTypeRef(page) {
    const mediaTypeRef = toRef(page, 'mediaType')

    if (!page.mediaWatcherStopers) {
      page.mediaWatcherStopers = []
    }

    page.mediaWatcherStopers.push(
      watch(mediaTypeRef, (v) => page.fetchers[MEDIA_TYPE_LABELS[v]]?.init(), { immediate: true }),
    )
    return mediaTypeRef
  }

  function getViewerIndex(index) {
    const page = getPage(index)

    if ([TAG_VIEWER, CREATOR_VIEWER].includes(page.type)) {
      const prevPage = getPage(index - 1)
      return prevPage.index
    }

    if (MAIN_VIEWER === page.type) {
      return page.index
    }

    throw new Error(
      `${viewerName}: Only PAGE.MAIN_VIEWER, PAGE.TAG_VIEWER and PAGE.CREATOR_VIEWER can get viewer index.`,
    )
  }

  function getAllFetcher(index) {
    return {
      foryou: getForyouFetcher(index),
      video: getVideoFetcher(index),
      photo: getPhotoFetcher(index),
      free: getFreeFetcher(index),
      subs: getSubsFetcher(index),
      shop: getShopFetcher(index),
    }
  }

  function getForyouFetcher(index) {
    return getMediaFetcher(index, 'foryou')
  }

  function getVideoFetcher(index) {
    return getMediaFetcher(index, 'video')
  }

  function getPhotoFetcher(index) {
    return getMediaFetcher(index, 'photo')
  }

  function getFreeFetcher(index) {
    return getMediaFetcher(index, 'free')
  }

  function getSubsFetcher(index) {
    return getMediaFetcher(index, 'subs')
  }

  function getShopFetcher(index) {
    return getMediaFetcher(index, 'shop')
  }

  function getMediaFetcher(index, type) {
    const page = getPage(index)

    if ([MAIN_VIEWER, TAG_LIST, CREATOR].includes(page.type)) {
      return page.fetchers[type]
    }

    if ([TAG_VIEWER, CREATOR_VIEWER].includes(page.type)) {
      const prevPage = getPage(index - 1)
      return prevPage.fetchers[type]
    }

    throw new Error(`${viewerName}: It's imposible to reach here...`)
  }

  function updateCreator(id) {
    // reviseStack 尚未被執行，將此執行放進 action queue 當中
    if (reviseStackTimeoutId !== null) {
      STACK_REVISING_ACTION_QUEUE.push(() => updateCreator(id))
      return
    }

    try {
      updateCreatorId(currIndex.value, id)
    } catch (e) {
      throw new Error(e.message)
    }
  }

  function updateNextCreator(id) {
    // 正在倒退回第一頁，不需要理會中間頁面的更新
    if (isBackingToFirst.value) return

    // reviseStack 尚未被執行，將此執行放進 action queue 當中
    if (reviseStackTimeoutId !== null) {
      STACK_REVISING_ACTION_QUEUE.push(() => updateNextCreator(id))
      return
    }

    try {
      updateCreatorId(currIndex.value + 1, id)
    } catch (e) {
      throw new Error(e.message)
    }
  }

  function updateCreatorId(index, id) {
    const page = getPage(index)

    if (page.type !== CREATOR) {
      throw new Error(`${viewerName}: Page at index ${index} is not PAGE.CREATOR`)
    }

    if (page.userId === id) {
      return
    }

    page.userId = id

    const media = getMedia(id)
    page.index = { free: 0, subs: 0, shop: 0 }
    page.fetchers = { free: media.free, subs: media.subs, shop: media.shop }
  }

  function reviseStack(fn) {
    reviseStackFn = fn

    // 預設時間內沒被執行就直接執行
    reviseStackTimeoutId = setTimeout(routeComplete, DEFAULT_REVISE_STACK_MILLISECOND)
  }

  function pushPage(type, data, mainViewerFetchers) {
    const page = {
      id: idMaker.next().value,
      type,
      ...data,
    }

    if (type === MAIN_VIEWER) {
      const { foryou, video, photo } = mainViewerFetchers
      const isForyou = !!foryou
      const isVideoPhoto = !!(!foryou && video && photo)
      const isOnlyVideo = !!(!foryou && video && !photo)
      const isOnlyPhoto = !!(!foryou && !video && photo)
      if (isForyou) {
        page.mediaType = MEDIA_TYPE.FORYOU
        page.index = { foryou: 0 }
        page.fetchers = { foryou }
        mainViewerType.value = MAIN_VIEWER_TYPE.FORYOU
      } else if (isVideoPhoto) {
        page.mediaType = MEDIA_TYPE.VIDEO
        page.index = { video: 0, photo: 0 }
        page.fetchers = { video, photo }
        mainViewerType.value = MAIN_VIEWER_TYPE.VIDEO_PHOTO
      } else if (isOnlyVideo) {
        page.mediaType = MEDIA_TYPE.VIDEO
        page.index = { video: 0 }
        page.fetchers = { video }
        mainViewerType.value = MAIN_VIEWER_TYPE.NONE
      } else if (isOnlyPhoto) {
        page.mediaType = MEDIA_TYPE.PHOTO
        page.index = { photo: 0 }
        page.fetchers = { photo }
        mainViewerType.value = MAIN_VIEWER_TYPE.NONE
      } else {
        throw new Error(
          'MainViewer is accept only three situation fetcher:\n1. Only one "foryou"\n2. Choose one "video" or "photo"\n3. Both "video" and "photo"',
        )
      }
    } else if (type === TAG_LIST) {
      page.index = { video: 0, photo: 0 }
      if (stack.value.length === 0) {
        page.mediaType = MEDIA_TYPE.VIDEO
      } else {
        let mediaExtendPage = getPage(currIndex.value)
        if ([TAG_VIEWER, CREATOR_VIEWER].includes(mediaExtendPage.type)) {
          mediaExtendPage = getPage(currIndex.value - 1)
        }
        const label = MEDIA_TYPE_LABELS[mediaExtendPage.mediaType]
        page.mediaType = mediaExtendPage.fetchers[label].dataList[mediaExtendPage.index[label]]?.type
      }
      page.fetchers = {
        video: useInfinite({
          request: API.Search.tagDetail,
          params: { type: MEDIA_TYPE.VIDEO, keyword: page.tag, ...(page.interest && { interest: page.interest }) },
        }),
        photo: useInfinite({
          request: API.Search.tagDetail,
          params: { type: MEDIA_TYPE.PHOTO, keyword: page.tag, ...(page.interest && { interest: page.interest }) },
        }),
      }
    } else if (CREATOR === type) {
      // page.mediaType = MEDIA_TYPE.SUBS
      page.mediaType = MEDIA_TYPE.FREE
      page.index = { free: 0, subs: 0, shop: 0 }
      if (page.userId) {
        const media = getMedia(page.userId)
        page.fetchers = { free: media.free, subs: media.subs, shop: media.shop }
      }
    }

    stack.value.push(page)
  }

  function popPage(restCount) {
    // pop 至剩餘頁數
    if (typeof restCount === 'number') {
      if (stack.value.length > restCount) {
        const pages = stack.value.splice(restCount)
        pages.forEach((page) => cleanPageEffect(page))
      }
    }

    // pop 最後一頁
    else {
      if (stack.value.length > 0) {
        const page = stack.value.pop()
        cleanPageEffect(page)
      }
    }
  }

  function cleanPageEffect(page) {
    page.mediaWatcherStopers?.forEach((stop) => stop())
  }

  function getPage(index) {
    const lastIndex = stack.value.length - 1
    if (index > lastIndex) {
      throw new Error(`${viewerName} viewer-router out of index "${index}" for last is "${stack.value.length - 1}"`)
    }
    return stack.value[index]
  }

  function changePath() {
    if (stack.value.length === 0) {
      return
    }
    const page = getPage(currIndex.value)
    switch (page?.type) {
      case TAG_LIST:
        if (page.catename) window.history.replaceState({}, '', `/tag/${page.catename}/${page.tag}`)
        else window.history.replaceState({}, '', `/tag/${page.tag}`)
        break
      case TAG_VIEWER:
        window.history.replaceState({}, '', `/tag/${getPage(currIndex.value - 1).tag}`)
        break
      case CREATOR:
        if (currIndex.value > 0) {
          const userName = getUserNameFromTriggerRouteFeed(currIndex.value - 1)
          window.history.replaceState({}, '', `/${userName}`)
        } else {
          const userName = page.userName
          window.history.replaceState({}, '', userName ? `/${userName}` : ORIGIN_PATH)
        }
        break
      case CREATOR_VIEWER:
        if (currIndex.value > 1) {
          const userName = getUserNameFromTriggerRouteFeed(currIndex.value - 2)
          window.history.replaceState({}, '', `/${userName}`)
        } else {
          const userName = getPage(currIndex.value - 1).userName
          window.history.replaceState({}, '', userName ? `/${userName}` : ORIGIN_PATH)
        }
        break
      default:
        revertPath()
        break
    }
  }

  function getTriggerRouteFeed(index) {
    if (index > -1) {
      let page = getPage(index)
      if ([TAG_VIEWER, CREATOR_VIEWER].includes(page.type) && index > 0) {
        page = getPage(index - 1)
      }
      const label = MEDIA_TYPE_LABELS[page.mediaType]
      return page.fetchers[label].dataList[page.index[label]]
    }
  }

  function getUserNameFromTriggerRouteFeed(index) {
    const page = getPage(index)
    if (page.type === MAIN_VIEWER) {
      const label = MEDIA_TYPE_LABELS[page.mediaType]
      return page.fetchers[label].dataList[page.index[label]]?.author?.username
    } else {
      return getTriggerRouteFeed(currIndex.value - 2)?.author?.username
    }
  }

  watch(currIndex, () => changePath())
  whenever(active, () => changePath())
  function setOriginPath(originPath) {
    ORIGIN_PATH = originPath
  }
  function revertPath() {
    window.history.replaceState({}, '', ORIGIN_PATH)
  }

  return {
    pages: readonly(stack),
    currIndex: readonly(currIndex),
    active,
    isBackingToFirst: readonly(isBackingToFirst),
    disableTransition: readonly(disableTransition),
    mainViewerType: readonly(mainViewerType),
    fpar: readonly(fpar),
    noData,

    // configure
    setFromMainViewer,
    setFromCreator,
    setFromTagList,
    setOriginPath,
    revertPath,

    // route
    next,
    prev,
    backToFirst,
    nextToTagList,
    nextToIndexedViewer,
    toIndexedMainViewer,

    // get
    getMediaType,
    getViewerIndex,
    getAllFetcher,
    getForyouFetcher,
    getVideoFetcher,
    getPhotoFetcher,
    getFreeFetcher,
    getSubsFetcher,
    getShopFetcher,

    // update
    updateCreator,
    updateNextCreator,
    setFPAR,
    transitFPAR,

    // notify
    routeComplete,
  }
}
